home *** CD-ROM | disk | FTP | other *** search
- Subject: v25i013: trn 2.0 - threaded newsreader based on rn 4.4, Part10/13
- Newsgroups: comp.sources.unix
- Approved: vixie@pa.dec.com
-
- Submitted-by: davison@borland.com (Wayne Davison)
- Posting-number: Volume 25, Issue 13
- Archive-name: trn/part10
-
- #! /bin/sh
- # This is a shell archive. Remove anything before this line, then unpack
- # it by saving it into a file and typing "sh file". To overwrite existing
- # files, type "sh file -c". You can also feed this as standard input via
- # unshar, or by typing "sh <file", e.g.. If this archive is complete, you
- # will see the following message at the end:
- # "End of archive 10 (of 13)."
- # Contents: mthreads.c ng.c
- # Wrapped by vixie@cognition.pa.dec.com on Tue Dec 3 16:34:56 1991
- PATH=/bin:/usr/bin:/usr/ucb ; export PATH
- if test -f 'mthreads.c' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'mthreads.c'\"
- else
- echo shar: Extracting \"'mthreads.c'\" \(33893 characters\)
- sed "s/^X//" >'mthreads.c' <<'END_OF_FILE'
- X/* $Id: mthreads.c,v 4.4.3.1 1991/11/22 04:12:15 davison Trn $
- X**
- X** $Log: mthreads.c,v $
- X** Revision 4.4.3.1 1991/11/22 04:12:15 davison
- X** Trn Release 2.0
- X**
- X*/
- X
- X/* mthreads.c -- for making and updating a discussion-thread database
- X**
- X** We use the active file to read the high/low counts for each newsgroup
- X** and compare them with the corresponding values in a file called active2
- X** (which we created to keep the database high/lows in one place). If they
- X** don't match, we read/update/write the group's thread file and move on.
- X** If the active2 file is missing or corrupted, it will be repaired in the
- X** normal course of operation.
- X**
- X** Usage: mthreads [-d[MM]] [-e[HHMM]] [-s[hsec]] [-aDfknv] [hierarchy_list]
- X*/
- X
- X#include "EXTERN.h"
- X#include "common.h"
- X#include "threads.h"
- X#ifdef SERVER
- X#include "server.h"
- X#endif
- X#include "INTERN.h"
- X#include "mthreads.h"
- X
- X#ifdef TZSET
- X#include <time.h>
- X#else
- X#include <sys/time.h>
- X#include <sys/timeb.h>
- X#endif
- X
- X#if !defined(FTRUNCATE) && !defined(CHSIZE)
- X# ifdef F_FREESP
- X# define MYCHSIZE
- X# else
- X# define MVTRUNC
- X# endif
- X#endif
- X
- X#ifdef USESYSLOG
- X#include <syslog.h>
- X#else
- XFILE *fp_log;
- X#endif
- X
- XFILE *fp_tmp, *fp_active, *fp_active2, *fp_active2w = Nullfp;
- bool eof_active = FALSE, eof_active2 = FALSE;
- long first, last, first2, last2;
- char ch, ch2;
- X
- struct stat filestat;
- X
- char buf[LBUFLEN+1];
- X
- static char line[256];
- static char line2[256];
- X
- char fmt_active2[] = "%s %010ld %07ld %c\n";
- X
- char *filename;
- X
- typedef struct _active_line {
- X struct _active_line *link;
- X char *name;
- X long last;
- X long first;
- X char type;
- X} ACTIVE_LINE;
- X
- X#define Nullact Null(ACTIVE_LINE*)
- X
- ACTIVE_LINE *line_root = Nullact, *last_line = Nullact, *pline = Nullact;
- X
- bool force_flag = FALSE, kill_mthreads = FALSE, no_processing = FALSE;
- bool add_new = FALSE, rebuild = FALSE, zap_thread = FALSE;
- bool acttimes_flag = FALSE, grevious_error;
- bool initializing = TRUE;
- int daemon_delay = 0, log_verbosity = 0, debug = 0, slow_down = 0;
- long expire_time = 0;
- char *hierarchy_list = NULL;
- long truncate_len = -1;
- X
- char nullstr[1] = "";
- X
- extern int locked, cron_locking;
- X
- X#ifdef TZSET
- time_t tnow;
- X#else
- struct timeb ftnow;
- X#endif
- X
- X#define TIMER_FIRST 1
- X#define TIMER_DEFAULT (10 * 60)
- X
- int processed_groups = 0, added_groups = 0, removed_groups = 0;
- int action;
- X
- X#define NG_DEFAULT 0
- X#define NG_MATCH 1
- X#define NG_SKIP 2
- X
- X#ifdef SERVER
- char *server;
- X#else
- time_t last_modified = 0;
- X#endif
- X
- SIGRET alarm_handler(), int_handler();
- bool makethreads ANSI((void));
- void log_startup ANSI((void));
- void log_stats ANSI((void));
- X
- int
- main(argc, argv)
- int argc;
- char *argv[];
- X{
- X int fd;
- X long pid;
- X
- X while (--argc) {
- X if (**++argv == '-') {
- X while (*++*argv) {
- X switch (**argv) {
- X case 'a': /* automatically thread new groups */
- X add_new = TRUE;
- X break;
- X case 'c': /* continue trying to lock */
- X cron_locking = TRUE;
- X break;
- X case 'D': /* run in debug mode */
- X debug++;
- X break;
- X case 'd': /* run in daemon mode */
- X if (*++*argv <= '9' && **argv >= '0') {
- X daemon_delay = atoi(*argv) * 60;
- X while (*++*argv <= '9' && **argv >= '0') {
- X ;
- X }
- X } else {
- X daemon_delay = TIMER_DEFAULT;
- X }
- X --*argv;
- X break;
- X case 'e': { /* enhanced expire processing */
- X struct tm *ts;
- X long desired;
- X
- X (void) time(&expire_time);
- X ts = localtime(&expire_time);
- X
- X if (*++*argv <= '9' && **argv >= '0') {
- X desired = atol(*argv);
- X if (desired/100 > 23 || desired%100 > 59) {
- X fprintf(stderr, "Illegal expire time: '%04d'\n",
- X desired);
- X exit(1);
- X }
- X desired = (desired/100)*60 + desired%100;
- X while (*++*argv <= '9' && **argv >= '0') {
- X ;
- X }
- X } else {
- X desired = 30; /* 0030 = 12:30am */
- X }
- X --*argv;
- X desired -= ts->tm_hour * 60 + ts->tm_min;
- X if (desired < 0) {
- X desired += 24 * 60;
- X }
- X expire_time += desired * 60 - ts->tm_sec;
- X break;
- X }
- X case 'f': /* force each group to process */
- X force_flag = TRUE;
- X break;
- X case 'k': /* kill running mthreads */
- X kill_mthreads = TRUE;
- X break;
- X case 'n': /* don't process anything */
- X no_processing = TRUE;
- X break;
- X case 's': /* sleep between articles */
- X if (*++*argv <= '9' && **argv >= '0') {
- X slow_down = atoi(*argv);
- X while (*++*argv <= '9' && **argv >= '0') {
- X ;
- X }
- X } else {
- X slow_down = 1L * 1000 * 1000;
- X }
- X --*argv;
- X break;
- X case 't': /* maintain active.times file */
- X#ifdef ACTIVE_TIMES
- X if (strEQ(ACTIVE_TIMES, "nntp")) {
- X fprintf(stderr, "Ignoring the -t option.\n");
- X } else {
- X acttimes_flag = TRUE;
- X }
- X#else
- X fprintf(stderr, "Ignoring the -t option.\n");
- X#endif
- X break;
- X case 'v': /* get more verbose in the log file */
- X log_verbosity++;
- X break;
- X case 'z': /* destroy .thread on severe signal */
- X zap_thread = TRUE;
- X break;
- X default:
- X fprintf(stderr, "Unknown option: '%c'\n", **argv);
- X exit(1);
- X }
- X }
- X } else {
- X if (hierarchy_list) {
- X fprintf(stderr, "Specify the newsgroups in one comma-separated list.\n");
- X exit(1);
- X }
- X hierarchy_list = *argv;
- X }
- X }
- X
- X /* Initialize umask(), file_exp(), etc. */
- X mt_init();
- X
- X /* If this is a kill request, look for the daemon lock file. */
- X if (kill_mthreads) {
- X if (!mt_lock(DAEMON_LOCK, SIGTERM)) {
- X fprintf(stderr, "No mthreads daemon is running.\n");
- X wrap_it_up(1);
- X }
- X fprintf(stderr, "Killed mthreads daemon.\n");
- X wrap_it_up(0);
- X }
- X
- X /* See if an mthreads pass is already going. */
- X if (mt_lock(PASS_LOCK, 0) != 0 && !daemon_delay) {
- X fprintf(stderr, "mthreads is already running.\n");
- X wrap_it_up(1);
- X }
- X
- X#ifdef USESYSLOG
- X# ifdef LOG_DAEMON
- X openlog("mthreads", LOG_PID, USESYSLOG);
- X# else
- X openlog("mthreads", LOG_PID);
- X# endif
- X#else
- X /* Open our log file */
- X filename = file_exp(MTLOG);
- X if ((fp_log = fopen(filename, "a")) == Nullfp) {
- X fprintf(stderr, "Unable to open `%s'.\n", filename);
- X wrap_it_up(1);
- X }
- X#endif
- X
- X#ifdef SIGHUP
- X if (sigset(SIGHUP, SIG_IGN) != SIG_IGN) {
- X sigset(SIGHUP, int_handler);
- X }
- X#endif
- X if (sigset(SIGINT, SIG_IGN) != SIG_IGN) {
- X sigset(SIGINT, int_handler);
- X }
- X#ifdef SIGQUIT
- X if (sigset(SIGQUIT, SIG_IGN) != SIG_IGN) {
- X sigset(SIGQUIT, int_handler);
- X }
- X#endif
- X sigset(SIGTERM, int_handler);
- X#ifdef SIGBUS
- X sigset(SIGBUS, int_handler);
- X#endif
- X sigset(SIGSEGV, int_handler);
- X#ifdef SIGTTIN
- X sigset(SIGTTIN, SIG_IGN);
- X sigset(SIGTTOU, SIG_IGN);
- X#endif
- X sigset(SIGALRM, SIG_IGN);
- X#ifdef SERVER
- X sigset(SIGPIPE, int_handler);
- X#endif
- X#ifdef lint
- X alarm_handler(); /* foolishness for lint's sake */
- X int_handler(SIGINT);
- X#endif
- X
- X /* Ensure this machine has the right byte-order for the database */
- X filename = file_exp(DBINIT);
- X if ((fp_tmp = fopen(filename, FOPEN_RB)) == Nullfp
- X || fread((char*)&mt_bmap,1,sizeof (BMAP), fp_tmp) < sizeof (BMAP)-1) {
- X if (fp_tmp != Nullfp) {
- X fclose(fp_tmp);
- X }
- X write_db_init:
- X mybytemap(&mt_bmap);
- X if ((fp_tmp = fopen(filename, FOPEN_WB)) == Nullfp) {
- X log_entry("Unable to create file: `%s'.\n", filename);
- X wrap_it_up(1);
- X }
- X mt_bmap.version = DB_VERSION;
- X fwrite((char*)&mt_bmap, 1, sizeof (BMAP), fp_tmp);
- X fclose(fp_tmp);
- X } else {
- X int i;
- X
- X fclose(fp_tmp);
- X if (mt_bmap.version != DB_VERSION) {
- X if (mt_bmap.version == DB_VERSION-1) {
- X rebuild = TRUE;
- X log_entry("Upgrading database to version %d.\n", DB_VERSION);
- X goto write_db_init;
- X }
- X log_entry("** Database is not the right version (%d instead of %d) **\n",
- X mt_bmap.version, DB_VERSION);
- X wrap_it_up(1);
- X }
- X mybytemap(&my_bmap);
- X for (i = 0; i < sizeof (LONG); i++) {
- X if (my_bmap.l[i] != mt_bmap.l[i]
- X || (i < sizeof (WORD) && my_bmap.w[i] != mt_bmap.w[i])) {
- X log_entry("\
- X** Byte-order conflict -- re-run from a compatible machine **\n\
- X\t\tor remove the current thread files, including db.init **\n");
- X wrap_it_up(1);
- X }
- X }
- X }
- X
- X#ifdef SERVER
- X if ((server = get_server_name(0)) == NULL) {
- X log_entry("Couldn't find name of news server.\n");
- X wrap_it_up(1);
- X }
- X#endif
- X
- X initializing = FALSE;
- X
- X /* If we're not in daemon mode, run through once and quit. */
- X if (!daemon_delay) {
- X log_startup();
- X setbuf(stdout, Nullch);
- X extra_expire = (expire_time != 0);
- X makethreads();
- X } else {
- X cron_locking = FALSE;
- X if (mt_lock(DAEMON_LOCK, 0) != 0) {
- X fprintf(stderr, "An mthreads daemon is already running.\n");
- X wrap_it_up(1);
- X }
- X /* For daemon mode, we cut ourself off from anything tty-related and
- X ** run in the background (involves forks, but no knives).
- X */
- X close(0);
- X if (open("/dev/null", 2) != 0) {
- X fprintf(stderr, "unable to open /dev/null!\n");
- X wrap_it_up(1);
- X }
- X close(1);
- X close(2);
- X dup(0);
- X dup(0);
- X while ((pid = fork()) < 0) {
- X sleep(2);
- X }
- X if (pid) {
- X exit(0);
- X }
- X#ifdef TIOCNOTTY
- X if ((fd = open("/dev/tty", 1)) >= 0) {
- X ioctl(fd, TIOCNOTTY, (int*)0);
- X close(fd);
- X }
- X#else
- X (void) setpgrp();
- X while ((pid = fork()) < 0) {
- X sleep(2);
- X }
- X if (pid) {
- X exit(0);
- X }
- X#endif
- X /* Put our pid in the lock file for death detection */
- X if ((fp_tmp = fopen(file_exp(MTDLOCK), "w")) != Nullfp) {
- X fprintf(fp_tmp, "%ld\n", (long)getpid());
- X fclose(fp_tmp);
- X }
- X log_startup();
- X
- X sigset(SIGALRM, alarm_handler);
- X
- X /* Start timer -- first interval is shorter than all others */
- X alarm(TIMER_FIRST);
- X for (;;) {
- X pause(); /* let alarm go off */
- X alarm(0);
- X
- X#ifndef USESYSLOG
- X /* Re-open our log file, if needed */
- X if (!fp_log && !(fp_log = fopen(file_exp(MTLOG), "a"))) {
- X wrap_it_up(1);
- X }
- X#endif
- X#ifndef SERVER
- X if (stat(file_exp(ACTIVE), &filestat) < 0) {
- X log_entry("Unable to stat active file -- quitting.\n");
- X wrap_it_up(1);
- X }
- X#endif
- X if (expire_time && time(Null(time_t*)) > expire_time) {
- X expire_time += 24L * 60 * 60;
- X extra_expire = TRUE;
- X }
- X#ifdef SERVER
- X makethreads(); /* NNTP version always compares files */
- X#else
- X if (extra_expire || filestat.st_mtime != last_modified) {
- X last_modified = filestat.st_mtime;
- X if (!makethreads()) {
- X last_modified--;
- X }
- X }
- X#endif
- X alarm(daemon_delay);
- X#ifndef USESYSLOG
- X fclose(fp_log); /* close the log file while we sleep */
- X fp_log = Nullfp;
- X#endif
- X } /* for */
- X }/* if */
- X
- X wrap_it_up(0);
- X return 0; /* NOTREACHED */
- X}
- X
- SIGRET
- alarm_handler()
- X{
- X sigset(SIGALRM, alarm_handler);
- X}
- X
- SIGRET
- int_handler(sig)
- int sig;
- X{
- X static int visits = 0;
- X int ret = 0;
- X
- X if (++visits > 4) {
- X wrap_it_up(1);
- X }
- X
- X#ifndef USESYSLOG
- X /* Re-open our log file, if needed */
- X if (fp_log || (fp_log = fopen(file_exp(MTLOG), "a")))
- X#endif
- X {
- X switch (sig) {
- X case SIGTERM:
- X#ifdef SIGHUP
- X case SIGHUP:
- X#endif
- X#ifdef SIGQUIT
- X case SIGQUIT:
- X#endif
- X log_entry("halt requested.\n");
- X zap_thread = 0;
- X break;
- X#ifdef SERVER
- X case SIGPIPE:
- X log_entry("broken pipe -- trying to continue.\n");
- X sigset(SIGPIPE, int_handler);
- X return;
- X#endif
- X#ifdef SIGBUS
- X case SIGBUS:
- X#endif
- X case SIGSEGV:
- X log_error("** Severe signal: %d **\n", sig);
- X /* Destroy offending thread file if requested to do so. */
- X if (zap_thread) {
- X unlink(thread_name(line));
- X log_entry("Destroyed thread file for %s\n", line);
- X }
- X break;
- X default:
- X log_entry("interrupt %d received.\n", sig);
- X zap_thread = 0;
- X ret = 1;
- X break;
- X }
- X }
- X if (!daemon_delay) {
- X printf("Interrupt %d!\n", sig);
- X if (zap_thread) {
- X printf("Destroyed thread file for %s\n", line);
- X }
- X }
- X
- X /* If we're in the middle of writing the new active2 file, finish it. */
- X if (fp_active2w) {
- X if (*line2) {
- X if (index(line2, ' ')) {
- X fputs(line2, fp_active2w);
- X } else {
- X fprintf(fp_active2w, fmt_active2, line2, last2, first2, ch2);
- X }
- X }
- X for (pline = line_root; pline; pline = pline->link) {
- X fprintf(fp_active2w, fmt_active2,
- X pline->name, pline->last, pline->first, pline->type);
- X }
- X if (!eof_active2) {
- X while (fgets(line2, sizeof line2, fp_active2)) {
- X fputs(line2, fp_active2w);
- X }
- X }
- X log_stats();
- X }
- X wrap_it_up(ret);
- X}
- X
- void
- wrap_it_up(ret)
- int ret;
- X{
- X mt_unlock(locked);
- X exit(ret);
- X}
- X
- X/* Process the active file, creating/modifying the active2 file and
- X** creating/modifying the thread data files.
- X*/
- bool
- makethreads()
- X{
- X register char *cp, *cp2;
- X char data_file_open;
- X bool update_successful, old_groups, touch_thread;
- X#ifdef SERVER
- X int server_failure = 0;
- X#endif
- X
- X /* See if an mthreads pass is already going. */
- X if (!(locked & PASS_LOCK) && mt_lock(PASS_LOCK, 0) != 0) {
- X log_entry("unable to get a lock for this pass.\n");
- X return FALSE;
- X }
- X#ifdef SERVER
- X if (!open_server()) {
- X return FALSE;
- X }
- X put_server("LIST"); /* ask server for the active file */
- X get_server(line, sizeof line);
- X if (*line != CHAR_OK) {
- X log_entry("Unable to get active file from server.\n");
- X close_server();
- X return FALSE;
- X }
- X if ((fp_active = fopen(file_exp(ACTIVE1), "w+")) == Nullfp) {
- X log_entry("Unable to write the active1 file -- quitting.\n");
- X wrap_it_up(1);
- X }
- X while (1) {
- X if (get_server(line, sizeof line) < 0) {
- X log_entry("Server failed to send entire active file.\n");
- X fclose(fp_active);
- X close_server();
- X return FALSE;
- X }
- X if (*line == '.') {
- X break;
- X }
- X fputs(line, fp_active);
- X putc('\n', fp_active);
- X }
- X if (ferror(fp_active)) {
- X log_entry("Error writing to active1 file.\n");
- X fclose(fp_active);
- X close_server();
- X return FALSE;
- X }
- X fseek(fp_active, 0L, 0); /* rewind for read */
- X#else /* not SERVER */
- X if ((fp_active = fopen(file_exp(ACTIVE), "r")) == Nullfp) {
- X log_entry("Unable to open the active file.\n");
- X wrap_it_up(1);
- X }
- X#endif
- X filename = file_exp(ACTIVE2);
- X if ((fp_active2w = fopen(filename, "r+")) == Nullfp) {
- X if ((fp_active2w = fopen(filename, "w")) == Nullfp) {
- X log_entry("Unable to open the active2 file for update.\n");
- X wrap_it_up(1);
- X }
- X /* Add existing groups to active.times file with ancient date. */
- X old_groups = TRUE;
- X } else {
- X old_groups = FALSE;
- X }
- X if ((fp_active2 = fopen(filename, "r")) == Nullfp) {
- X log_entry("Unable to open the active2 file.\n");
- X wrap_it_up(1);
- X }
- X if (extra_expire && log_verbosity) {
- X log_entry("Using enhanced expiration for this pass.\n");
- X }
- X
- X /* What time is it? */
- X#ifdef TZSET
- X (void) time(&tnow);
- X (void) tzset();
- X#else
- X (void) ftime(&ftnow);
- X#endif
- X
- X eof_active = eof_active2 = FALSE;
- X fp_tmp = Nullfp;
- X
- X /* Loop through entire active file. */
- X for (;;) {
- X if (eof_active || !fgets(line, sizeof line, fp_active)) {
- X if (eof_active2 && !line_root) {
- X break;
- X }
- X eof_active = TRUE;
- X ch = 'x';
- X } else {
- X cp = line + strlen(line) - 1;
- X if (*cp == '\n') {
- X *cp = '\0';
- X }
- X if (!(cp = index(line, ' '))) {
- X log_entry("** line in 'active' has no space: %s **\n", line);
- X continue;
- X }
- X *cp = '\0';
- X if (sscanf(cp+1, "%ld %ld %c", &last, &first, &ch) != 3) {
- X log_entry("** digits corrupted in 'active': %s %s **\n",
- X line, cp+1);
- X continue;
- X }
- X if (last < first - 1) {
- X log_entry("** bogus group values in 'active': %s %s **\n",
- X line, cp+1);
- X continue;
- X }
- X }
- X if (debug > 1 || log_verbosity > 3) {
- X log_entry("Processing %s:\n", line);
- X }
- X data_file_open = 0;
- X /* If we've allocated some lines in memory while searching for
- X ** newsgroups (they've scrambled the active file on us), check
- X ** them first.
- X */
- X last_line = Nullact;
- X for (pline = line_root; pline; pline = pline->link) {
- X if (eof_active || strEQ(line, pline->name)) {
- X strcpy(line2, pline->name);
- X free(pline->name);
- X first2 = pline->first;
- X last2 = pline->last;
- X ch2 = pline->type;
- X if (last_line) {
- X last_line->link = pline->link;
- X } else {
- X line_root = pline->link;
- X }
- X free(pline);
- X break;
- X }
- X last_line = pline;
- X }/* for */
- X touch_thread = FALSE;
- X
- X /* If not found yet, check the active2 file. */
- X if (!pline) {
- X for (;;) {
- X if (eof_active2 || !fgets(line2, sizeof line2, fp_active2)) {
- X /* At end of file, check if the thread data file exists.
- X ** If so, use its high/low values. Else, default to
- X ** some initial values.
- X */
- X eof_active2 = TRUE;
- X if (eof_active) {
- X break;
- X }
- X strcpy(line2, line);
- X if ((data_file_open = init_data(thread_name(line)))) {
- X last2 = total.last;
- X first2 = total.first;
- X ch2 = 'y';
- X } else {
- X total.first = first2 = first;
- X if (add_new && (!hierarchy_list
- X || ngmatch(hierarchy_list, line) == NG_MATCH)) {
- X total.last = last2 = first - 1;
- X ch2 = (ch == '=' ? 'x' : ch);
- X touch_thread = TRUE;
- X added_groups += (ch2 != 'x');
- X } else {
- X total.last = last2 = last;
- X ch2 = (ch == '=' ? 'X' : toupper(ch));
- X }
- X }
- X data_file_open++; /* (1 == empty, 2 == open) */
- X#ifdef ACTIVE_TIMES
- X /* Found a new group -- see if we need to log it. */
- X if (acttimes_flag) {
- X if (!fp_tmp && !(fp_tmp = fopen(ACTIVE_TIMES, "a"))) {
- X log_entry("unable to append to %s.\n",ACTIVE_TIMES);
- X acttimes_flag = FALSE;
- X } else {
- X fprintf(fp_tmp, "%s %ld mthreads@%s\n", line,
- X old_groups ? 30010440L : time(Null(time_t)),
- X OURDOMAIN);
- X }
- X }
- X#endif
- X break;
- X }
- X if (!(cp2 = index(line2, ' '))) {
- X log_entry("active2 line has no space: %s\n", line2);
- X continue;
- X }
- X *cp2 = '\0';
- X if (sscanf(cp2+1,"%ld %ld %c",&last2,&first2,&ch2) != 3) {
- X log_entry("active2 digits corrupted: %s %s\n",
- X line2, cp2+1);
- X continue;
- X }
- X /* Check if we're still in-sync */
- X if (eof_active || strEQ(line, line2)) {
- X break;
- X }
- X /* Nope, we've got to go looking for this line somewhere
- X ** down in the file. Save each non-matching line in memory
- X ** as we go.
- X */
- X pline = (ACTIVE_LINE*)safemalloc(sizeof (ACTIVE_LINE));
- X pline->name = savestr(line2);
- X pline->last = last2;
- X pline->first = first2;
- X pline->type = ch2;
- X pline->link = Nullact;
- X if (!last_line) {
- X line_root = pline;
- X } else {
- X last_line->link = pline;
- X }
- X *line2 = '\0';
- X last_line = pline;
- X }/* for */
- X if (eof_active && eof_active2) {
- X break;
- X }
- X }/* if !pline */
- X if (eof_active) {
- X strcpy(line, line2);
- X if (truncate_len < 0) {
- X truncate_len = ftell(fp_active2w);
- X }
- X }
- X if (rebuild) {
- X unlink(thread_name(line));
- X }
- X update_successful = FALSE;
- X if (hierarchy_list && !add_new) {
- X switch ((action = ngmatch(hierarchy_list, line))) {
- X case NG_MATCH: /* if unthreaded, add it */
- X if (ch2 < 'a' && ch != 'x' && ch != '=') {
- X total.last = last2 = first2 - 1;
- X touch_thread = TRUE;
- X added_groups++;
- X }
- X break;
- X case NG_SKIP: /* if threaded, remove it */
- X if (ch2 >= 'a') {
- X unlink(thread_name(line));
- X removed_groups++;
- X }
- X break;
- X }
- X } else {
- X action = (ch2 < 'a' ? NG_SKIP : NG_MATCH);
- X }
- X if (action == NG_DEFAULT || (debug && action == NG_SKIP)) {
- X dont_read_data(data_file_open); /* skip silently */
- X if (touch_thread) {
- X (void) write_data(thread_name(line));
- X }
- X } else if (ch == 'x' || ch == '=') {
- X if (!daemon_delay) { /* skip 'x'ed groups */
- X putchar('x');
- X }
- X ch = (action == NG_SKIP ? 'X' : 'x');
- X if ((ch2 >= 'a' && ch2 != 'x') || force_flag) {
- X /* Remove thread file if group is newly 'x'ed out */
- X unlink(thread_name(line));
- X }
- X update_successful = TRUE;
- X dont_read_data(data_file_open);
- X } else if (action == NG_SKIP) { /* skip excluded groups */
- X if (!daemon_delay) {
- X putchar('X');
- X }
- X ch = toupper(ch);
- X if (force_flag) {
- X unlink(thread_name(line));
- X }
- X update_successful = TRUE;
- X dont_read_data(data_file_open);
- X } else if (no_processing) {
- X if (!daemon_delay) {
- X putchar(',');
- X }
- X ch2 = ch;
- X dont_read_data(data_file_open);
- X if (touch_thread) {
- X (void) write_data(thread_name(line));
- X }
- X } else if (!force_flag && !extra_expire && !rebuild
- X && first == first2 && last == last2) {
- X /* We're up-to-date here. Skip it. */
- X if (!daemon_delay) {
- X putchar('.');
- X }
- X update_successful = TRUE;
- X dont_read_data(data_file_open);
- X if (touch_thread) {
- X (void) write_data(thread_name(line));
- X }
- X } else {
- X /* Looks like we need to process something. */
- X#ifdef SERVER
- X if (server_failure == 1) {
- X if (!open_server()) {
- X log_entry("failed to re-open server for this pass.\n");
- X server_failure = 2;
- X } else {
- X server_failure = 0;
- X }
- X }
- X if (!server_failure) {
- X sprintf(buf, "GROUP %s", line);
- X put_server(buf); /* go to next group */
- X if (get_server(buf, sizeof buf) < 0
- X || *buf != CHAR_OK) {
- X log_error("NNTP failure -- %s.\n", buf);
- X if (strnNE(buf, "400", 3)) {
- X close_server();
- X }
- X server_failure = 1;
- X }
- X }
- X if (server_failure) {
- X#else
- X strcpy(cp = buf, line2);
- X while ((cp = index(cp, '.'))) {
- X *cp = '/';
- X }
- X filename = file_exp(buf); /* relative to spool dir */
- X if (chdir(filename) < 0) {
- X if (errno != ENOENT) {
- X log_entry("Unable to chdir to `%s'.\n", filename);
- X }
- X#endif
- X if (!daemon_delay) {
- X putchar('*');
- X }
- X dont_read_data(data_file_open);
- X } else {
- X filename = thread_name(line);
- X /* Try to open the data file only if we didn't try it
- X ** in the name matching code above.
- X */
- X if (!data_file_open--) { /* (0 == haven't tried yet) */
- X if (!(data_file_open = init_data(filename))) {
- X total.last = first - 1;
- X total.first = first;
- X }
- X }
- X
- X if (data_file_open) { /* (0 == empty, 1 == open) */
- X if (!read_data()) { /* did read fail? */
- X if (debug) {
- X strcpy(buf, filename);
- X cp = rindex(buf, '/') + 1;
- X strcpy(cp, "bad.read");
- X rename(filename, buf);
- X }
- X data_file_open = init_data(Nullch);
- X total.last = first - 1;
- X total.first = first;
- X }
- X }
- X grevious_error = FALSE;
- X process_articles(first, last);
- X processed_groups++;
- X if (!added_count && !expired_count && !touch_thread
- X && last == last2) {
- X (void) write_data(Nullch);
- X if (!daemon_delay) {
- X putchar(':');
- X }
- X update_successful = TRUE;
- X } else {
- X strcpy(buf, filename);
- X cp = rindex(buf, '/') + 1;
- X strcpy(cp, NEW_THREAD); /* write data as .new */
- X if (write_data(buf) && !grevious_error) {
- X rename(buf, filename);
- X added_articles += added_count;
- X expired_articles += expired_count;
- X if (!daemon_delay) {
- X if (!total.root) {
- X putchar('-');
- X } else {
- X putchar('#');
- X }
- X }
- X update_successful = TRUE;
- X } else {
- X if (debug) {
- X cp = rindex(filename, '/') + 1;
- X strcpy(cp, "bad.write");
- X rename(buf, filename);
- X } else {
- X unlink(buf); /* blow away bad write */
- X if (grevious_error) {
- X unlink(filename); /* blow away the .thread, */
- X (void) init_data(Nullch); /* zero totals & */
- X (void) write_data(filename); /* write it null */
- X }
- X }
- X if (!daemon_delay) {
- X putchar('!');
- X }
- X }/* if */
- X }/* if */
- X }/* if */
- X }/* if */
- X /* Finally, update the active2 entry for this newsgroup. */
- X if (!eof_active) {
- X if (update_successful) {
- X fprintf(fp_active2w, fmt_active2, line, last, first, ch);
- X } else {
- X fprintf(fp_active2w, fmt_active2, line, last2, first2, ch2);
- X }
- X }
- X *line2 = '\0';
- X /* If we're not out of sync, keep active2 file flushed. */
- X if (!line_root) {
- X fflush(fp_active2w);
- X }
- X#ifdef CHECKLOAD
- X checkload();
- X#endif
- X }/* for */
- X
- X#ifdef SERVER
- X if (!server_failure) {
- X close_server();
- X }
- X#endif
- X fclose(fp_active);
- X fclose(fp_active2);
- X
- X if (truncate_len >= 0) {
- X#ifdef FTRUNCATE
- X if (ftruncate(fileno(fp_active2w), truncate_len) == -1)
- X#else
- X#ifdef MVTRUNC
- X if (mvtrunc(file_exp(ACTIVE2), truncate_len) == -1)
- X#else
- X if (chsize(fileno(fp_active2w), truncate_len) == -1)
- X#endif
- X#endif
- X {
- X log_entry("Unable to truncate the active2 file.\n");
- X }
- X truncate_len = -1;
- X }
- X fclose(fp_active2w);
- X fp_active2w = Nullfp;
- X
- X if (fp_tmp) {
- X fclose(fp_tmp);
- X }
- X log_stats();
- X processed_groups = added_groups = removed_groups = 0;
- X added_articles = expired_articles = 0;
- X
- X extra_expire = FALSE;
- X rebuild = FALSE;
- X
- X mt_unlock(PASS_LOCK); /* remove single-pass lock */
- X
- X return TRUE;
- X}
- X
- X#ifdef SERVER
- int
- open_server()
- X{
- X switch (server_init(server)) {
- X case OK_NOPOST:
- X case OK_CANPOST:
- X return 1;
- X case ERR_ACCESS:
- X log_entry("Server %s rejected connection -- quitting.\n", server);
- X wrap_it_up(1);
- X default:
- X log_entry("Couldn't connect with server %s.\n", server);
- X return 0;
- X }
- X}
- X#endif
- X
- X/*
- X** ngmatch - newsgroup name matching
- X**
- X** returns NG_MATCH for a positive patch, NG_SKIP for a negative match,
- X** and NG_DEFAULT if the group doesn't match at all.
- X**
- X** "all" in a pattern is a wildcard that matches exactly one word;
- X** it does not cross "." (NGDELIM) delimiters.
- X**
- X** This matching code was borrowed from C news.
- X*/
- X
- X#define ALL "all" /* word wildcard */
- X
- X#define NGNEG '!'
- X#define NGSEP ','
- X#define NGDELIM '.'
- X
- int
- ngmatch(ngpat, grp)
- char *ngpat, *grp;
- X{
- X register char *patp; /* point at current pattern */
- X register char *patcomma;
- X register int depth;
- X register int faildeepest = 0, hitdeepest = 0; /* in case no match */
- X register bool negation;
- X
- X for (patp = ngpat; patp != Nullch; patp = patcomma) {
- X negation = FALSE;
- X patcomma = index(patp, NGSEP);
- X if (patcomma != Nullch) {
- X *patcomma = '\0'; /* will be restored below */
- X }
- X if (*patp == NGNEG) {
- X ++patp;
- X negation = TRUE;
- X }
- X depth = onepatmatch(patp, grp); /* try 1 pattern, 1 group */
- X if (patcomma != Nullch) {
- X *patcomma++ = NGSEP; /* point after the comma */
- X }
- X if (depth == 0) { /* mis-match */
- X ; /* ignore it */
- X } else if (negation) {
- X /* record depth of deepest negated matched word */
- X if (depth > faildeepest) {
- X faildeepest = depth;
- X }
- X } else {
- X /* record depth of deepest plain matched word */
- X if (depth > hitdeepest) {
- X hitdeepest = depth;
- X }
- X }
- X }
- X if (hitdeepest > faildeepest) {
- X return NG_MATCH;
- X } else if (faildeepest) {
- X return NG_SKIP;
- X } else {
- X return NG_DEFAULT;
- X }
- X}
- X
- X/*
- X** Match a pattern against a group by looking at each word of pattern in turn.
- X**
- X** On a match, return the depth (roughly, ordinal number * k) of the rightmost
- X** word that matches. If group runs out first, the match fails; if pattern
- X** runs out first, it succeeds. On a failure, return zero.
- X*/
- int
- onepatmatch(patp, grp)
- char *patp, *grp;
- X{
- X register char *rpatwd; /* used by word match (inner loop) */
- X register char *patdot, *grdot; /* point at dots after words */
- X register char *patwd, *grwd; /* point at current words */
- X register int depth = 0;
- X
- X for (patwd = patp, grwd = grp;
- X patwd != Nullch && grwd != Nullch;
- X patwd = patdot, grwd = grdot
- X ) {
- X register bool match = FALSE;
- X register int incr = 20;
- X
- X /* null-terminate words */
- X patdot = index(patwd, NGDELIM);
- X if (patdot != Nullch) {
- X *patdot = '\0'; /* will be restored below */
- X }
- X grdot = index(grwd, NGDELIM);
- X if (grdot != Nullch) {
- X *grdot = '\0'; /* will be restored below */
- X }
- X /*
- X * Match one word of pattern with one word of group.
- X * A pattern word of "all" matches any group word,
- X * but isn't worth as much.
- X */
- X#ifdef FAST_STRCMP
- X match = STREQ(patwd, grwd);
- X if (!match && STREQ(patwd, ALL)) {
- X match = TRUE;
- X --incr;
- X }
- X#else
- X for (rpatwd = patwd; *rpatwd == *grwd++;) {
- X if (*rpatwd++ == '\0') {
- X match = TRUE; /* literal match */
- X break;
- X }
- X }
- X if (!match) {
- X /* ugly special case match for "all" */
- X rpatwd = patwd;
- X if (*rpatwd++ == 'a' && *rpatwd++ == 'l'
- X && *rpatwd++ == 'l' && *rpatwd == '\0') {
- X match = TRUE;
- X --incr;
- X }
- X }
- X#endif /* FAST_STRCMP */
- X
- X if (patdot != Nullch) {
- X *patdot++ = NGDELIM; /* point after the dot */
- X }
- X if (grdot != Nullch) {
- X *grdot++ = NGDELIM;
- X }
- X if (!match) {
- X depth = 0; /* words differed - mismatch */
- X break;
- X }
- X depth += incr;
- X }
- X /* if group name ran out before pattern, then match fails */
- X if (grwd == Nullch && patwd != Nullch) {
- X depth = 0;
- X }
- X return depth;
- X}
- X
- X/* Put our startup options into the log file.
- X*/
- void
- log_startup()
- X{
- X char tmpbuf[256];
- X
- X strcpy(tmpbuf, "Started mthreads");
- X if (cron_locking) {
- X strcat(tmpbuf, " -c");
- X }
- X if (debug) {
- X strcat(tmpbuf, " -D");
- X }
- X if (force_flag) {
- X strcat(tmpbuf, " -f");
- X }
- X if (daemon_delay) {
- X sprintf(tmpbuf + strlen(tmpbuf), " -d%d", daemon_delay / 60);
- X if (expire_time) {
- X struct tm *ts;
- X
- X ts = localtime(&expire_time);
- X sprintf(tmpbuf + strlen(tmpbuf), " -e%02d%02d",ts->tm_hour,ts->tm_min);
- X }
- X } else if (expire_time) {
- X strcat(tmpbuf, " -e");
- X }
- X if (slow_down) {
- X sprintf(tmpbuf + strlen(tmpbuf), " -s%d", slow_down);
- X }
- X if (no_processing) {
- X strcat(tmpbuf, " -n");
- X }
- X if (log_verbosity) {
- X sprintf(tmpbuf + strlen(tmpbuf), " -v%d", log_verbosity);
- X }
- X if (zap_thread) {
- X strcat(tmpbuf, " -z");
- X }
- X if (add_new) {
- X if (hierarchy_list) {
- X sprintf(tmpbuf + strlen(tmpbuf), " -a %s", hierarchy_list);
- X } else {
- X strcat(tmpbuf, " -a all");
- X }
- X } else if (hierarchy_list) {
- X sprintf(tmpbuf + strlen(tmpbuf), " %s (only)", hierarchy_list);
- X }
- X log_entry("%s\n", tmpbuf);
- X}
- X
- X/* Put our statistics into the log file.
- X*/
- void
- log_stats()
- X{
- X sprintf(line, "Processed %d group%s: added %d article%s, expired %d.\n",
- X processed_groups, processed_groups == 1 ? nullstr : "s",
- X added_articles, added_articles == 1 ? nullstr : "s",
- X expired_articles);
- X
- X log_entry(line);
- X
- X if (!daemon_delay) {
- X putchar('\n');
- X fputs(line, stdout);
- X }
- X if (added_groups) {
- X sprintf(line, "Turned %d group%s on.\n", added_groups,
- X added_groups == 1 ? nullstr : "s");
- X log_entry(line);
- X if (!daemon_delay) {
- X fputs(line, stdout);
- X }
- X }
- X if (removed_groups) {
- X sprintf(line, "Turned %d group%s off.\n", removed_groups,
- X removed_groups == 1 ? nullstr : "s");
- X log_entry(line);
- X if (!daemon_delay) {
- X fputs(line, stdout);
- X }
- X }
- X}
- X/* Generate a log entry with timestamp.
- X*/
- X/*VARARGS1*/
- void
- log_entry(fmt, arg1, arg2, arg3)
- char *fmt;
- long arg1, arg2, arg3;
- X{
- X#ifndef USESYSLOG
- X time_t now;
- X char *ctime();
- X#endif
- X
- X if (initializing) {
- X fprintf(stderr, fmt, arg1, arg2, arg3);
- X return;
- X }
- X
- X#ifndef USESYSLOG
- X (void) time(&now);
- X fprintf(fp_log, "%.12s%c", ctime(&now)+4, daemon_delay ? ' ' : '+');
- X fprintf(fp_log, fmt, arg1, arg2, arg3);
- X fflush(fp_log);
- X#else
- X syslog(LOG_INFO, fmt, arg1, arg2, arg3);
- X#endif
- X}
- X
- X/* Generate a log entry, with 'E'rror flagging (non-daemon mode), time-stamp,
- X** and newsgroup name.
- X*/
- X/*VARARGS1*/
- void
- log_error(fmt, arg1, arg2, arg3)
- char *fmt;
- long arg1;
- long arg2;
- long arg3;
- X{
- X char fmtbuf[256];
- X
- X sprintf(fmtbuf, "%s: %s", line, fmt);
- X#ifndef USESYSLOG
- X log_entry(fmtbuf, arg1, arg2, arg3);
- X#else
- X syslog(LOG_NOTICE, fmtbuf, arg1, arg2, arg3);
- X#endif
- X if (*fmt == '*') {
- X grevious_error = TRUE;
- X if (!daemon_delay) {
- X putchar('E');
- X }
- X }
- X else {
- X if (!daemon_delay) {
- X putchar('e');
- X }
- X }
- X}
- X
- X#ifdef MYCHSIZE
- X /* code courtesy of William Kucharski */
- X
- int
- chsize(fd, length)
- int fd; /* file descriptor */
- off_t length; /* length to set file to */
- X{
- X extern long lseek();
- X struct flock fl;
- X
- X if (fstat(fd, &filestat) < 0) {
- X return -1;
- X }
- X if (filestat.st_size < length) { /* extend file length */
- X /* Write a 0 byte at the end. */
- X if (lseek(fd, length - 1, 0) < 0
- X || write(fd, "", 1) != 1) {
- X return -1;
- X }
- X } else {
- X /* Truncate file at length. */
- X fl.l_whence = 0;
- X fl.l_len = 0;
- X fl.l_start = length;
- X fl.l_type = F_WRLCK; /* write lock on file space */
- X
- X /*
- X ** This relies on the UNDOCUMENTED F_FREESP argument to
- X ** fcntl(2), which truncates the file so that it ends at the
- X ** position indicated by fl.l_start.
- X **
- X ** Will minor miracles never cease?
- X */
- X if (fcntl(fd, F_FREESP, &fl) < 0) {
- X return -1;
- X }
- X }
- X return 0;
- X}
- X#endif
- X
- X#ifdef MVTRUNC
- int
- mvtrunc(filename, truncate_len)
- char *filename;
- long truncate_len;
- X{
- X FILE *fp_in, *fp_out;
- X
- X sprintf(line, "%s.new", filename);
- X if ((fp_out = fopen(line, "w")) == Nullfp) {
- X log_entry("Tried to create active2.new.\n");
- X return -1;
- X }
- X if ((fp_in = fopen(filename, "r")) == Nullfp) {
- X fclose(fp_out);
- X unlink(line);
- X log_entry("Tried to re-open the active2 file.\n");
- X return -1;
- X }
- X while (ftell(fp_out) < truncate_len) {
- X if (!fgets(buf, sizeof buf, fp_in)) {
- X break;
- X }
- X fputs(buf, fp_out);
- X }
- X sprintf(buf, "%s.old", filename);
- X rename(filename, buf);
- X rename(line, filename);
- X fclose(fp_in);
- X fclose(fp_out);
- X
- X return 0;
- X}
- X#endif
- X
- X#ifndef RENAME
- int
- rename(old, new)
- char *old, *new;
- X{
- X struct stat st;
- X
- X if (stat(old, &st) == -1) {
- X return -1;
- X }
- X if (unlink(new) == -1 && errno != ENOENT) {
- X return -1;
- X }
- X if (link(old, new) == -1) {
- X return -1;
- X }
- X if (unlink(old) == -1) {
- X int e = errno;
- X (void) unlink(new);
- X errno = e;
- X return -1;
- X }
- X return 0;
- X}
- X#endif /*RENAME*/
- X
- X#ifdef CHECKLOAD
- X
- X#define FREQCHECK 300
- X#define SLPLOAD 300
- X#define UPTIME "/usr/ucb/uptime"
- X#define TOOHIGH 5
- X
- checkload()
- X{
- X static long lastcheck = 0;
- X long time(), i;
- X FILE *pp;
- X char buf[BUFSIZ];
- X register char *cp;
- X char *strrchr();
- X
- X i = time(Null(time_t*));
- X if ((i - lastcheck) < FREQCHECK) {
- X return;
- X }
- X lastcheck = i;
- X
- again:
- X if ((pp = popen(UPTIME, "r")) == NULL) {
- X return;
- X }
- X if (fgets(buf, BUFSIZ, pp) == NULL) {
- X pclose(pp);
- X return;
- X }
- X pclose(pp);
- X if ((cp = strrchr(buf, ':')) == NULL) {
- X return;
- X }
- X if (atoi(cp + 2) >= TOOHIGH) {
- X sleep(SLPLOAD);
- X goto again;
- X } else {
- X return;
- X }
- X}
- X#endif
- END_OF_FILE
- if test 33893 -ne `wc -c <'mthreads.c'`; then
- echo shar: \"'mthreads.c'\" unpacked with wrong size!
- fi
- # end of 'mthreads.c'
- fi
- if test -f 'ng.c' -a "${1}" != "-c" ; then
- echo shar: Will not clobber existing file \"'ng.c'\"
- else
- echo shar: Extracting \"'ng.c'\" \(35048 characters\)
- sed "s/^X//" >'ng.c' <<'END_OF_FILE'
- X/* $Id: ng.c,v 4.4 1991/09/09 20:23:31 sob Exp sob $
- X *
- X * $Log: ng.c,v $
- X * Revision 4.4 1991/09/09 20:23:31 sob
- X * release 4.4
- X *
- X *
- X *
- X */
- X/* This software is Copyright 1991 by Stan Barber.
- X *
- X * Permission is hereby granted to copy, reproduce, redistribute or otherwise
- X * use this software as long as: there is no monetary profit gained
- X * specifically from the use or reproduction of this software, it is not
- X * sold, rented, traded or otherwise marketed, and this copyright notice is
- X * included prominently in any copy made.
- X *
- X * The author make no claims as to the fitness or correctness of this software
- X * for any use whatsoever, and it is provided as is. Any use of this software
- X * is at the user's own risk.
- X */
- X
- X#include "EXTERN.h"
- X#include "common.h"
- X#include "rn.h"
- X#include "term.h"
- X#include "final.h"
- X#include "util.h"
- X#include "artsrch.h"
- X#include "cheat.h"
- X#include "help.h"
- X#include "kfile.h"
- X#include "rcstuff.h"
- X#include "head.h"
- X#include "bits.h"
- X#include "art.h"
- X#include "artio.h"
- X#include "ngstuff.h"
- X#include "intrp.h"
- X#include "respond.h"
- X#include "ngdata.h"
- X#include "backpage.h"
- X#include "rcln.h"
- X#include "last.h"
- X#include "search.h"
- X#ifdef SERVER
- X#include "server.h"
- X#endif
- X#ifdef USETHREADS
- X#include "threads.h"
- X#include "rthreads.h"
- X#endif
- X#include "decode.h"
- X#include "INTERN.h"
- X#include "ng.h"
- X#include "artstate.h" /* somebody has to do it */
- X
- X/* art_switch() return values */
- X
- X#define AS_NORM 0
- X#define AS_INP 1
- X#define AS_ASK 2
- X#define AS_CLEAN 3
- X
- ART_NUM recent_art = -1; /* previous article # for '-' command */
- ART_NUM curr_art = -1; /* current article # */
- int exit_code = NG_NORM;
- X
- void
- ng_init()
- X{
- X
- X#ifdef KILLFILES
- X open_kfile(KF_GLOBAL);
- X#endif
- X#ifdef CUSTOMLINES
- X init_compex(&hide_compex);
- X init_compex(&page_compex);
- X#endif
- X}
- X
- X/* do newsgroup on line ng with name ngname */
- X
- X/* assumes that we are chdir'ed to SPOOL, and assures that that is
- X * still true upon return, but chdirs to SPOOL/ngname in between
- X *
- X * If you can understand this routine, you understand most of the program.
- X * The basic structure is:
- X * for each desired article
- X * for each desired page
- X * for each line on page
- X * if we need another line from file
- X * get it
- X * if it's a header line
- X * do special things
- X * for each column on page
- X * put out a character
- X * end loop
- X * end loop
- X * end loop
- X * end loop
- X *
- X * (Actually, the pager is in another routine.)
- X *
- X * The chief problem is deciding what is meant by "desired". Most of
- X * the messiness of this routine is due to the fact that people want
- X * to do unstructured things all the time. I have used a few judicious
- X * goto's where I thought it improved readability. The rest of the messiness
- X * arises from trying to be both space and time efficient. Have fun.
- X */
- X
- int
- do_newsgroup(start_command)
- char *start_command; /* command to fake up first */
- X{
- X#ifdef SERVER
- X char artname[MAXFILENAME];
- X char intrpwork[MAXFILENAME];
- X static long our_pid=0;
- X#endif /* SERVER */
- X char oldmode = mode;
- X register long i; /* scratch */
- X int skipstate; /* how many unavailable articles */
- X /* have we skipped already? */
- X
- X char *whatnext = "%sWhat next? [%s]";
- X
- X#ifdef SERVER
- X if (our_pid == 0) /* Agreed, this is gross */
- X our_pid = getpid();
- X#endif /* SERVER */
- X
- X#ifdef ARTSEARCH
- X srchahead = (scanon && ((ART_NUM)toread[ng]) >= scanon ? -1 : 0);
- X /* did they say -S? */
- X#endif
- X
- X mode = 'a';
- X#ifdef USETHREADS
- X recent_p_art = curr_p_art = Nullart;
- X#endif
- X recent_art = curr_art = -1;
- X exit_code = NG_NORM;
- X
- X#ifdef SERVER
- X sprintf(ser_line, "GROUP %s", ngname);
- X#ifdef DEBUGGING
- X if (debug & DEB_NNTP)
- X printf(">%s\n", ser_line) FLUSH;
- X#endif
- X put_server(ser_line);
- X if (nntp_get(ser_line, sizeof(ser_line)) < 0) {
- X fprintf(stderr, "\nrrn: Unexpected close of server socket.\n");
- X finalize(1);
- X }
- X#ifdef DEBUGGING
- X if (debug & DEB_NNTP)
- X printf("<%s\n", ser_line) FLUSH;
- X#endif
- X if (*ser_line != CHAR_OK) {
- X if (atoi(ser_line) != ERR_NOGROUP){
- X fprintf(stderr, "\nrrn: server response to GROUP %s:\n%s\n",
- X ngname, ser_line);
- X finalize(1);
- X }
- X return (-1);
- X }
- X#else /* not SERVER */
- X if (eaccess(ngdir,5)) { /* directory read protected? */
- X if (eaccess(ngdir,0)) {
- X#ifdef VERBOSE
- X IF(verbose)
- X printf("\nNewsgroup %s does not have a spool directory!\n",
- X ngname) FLUSH;
- X ELSE
- X#endif
- X#ifdef TERSE
- X printf("\nNo spool for %s!\n",ngname) FLUSH;
- X#endif
- X#ifdef CATCHUP
- X catch_up(ng);
- X#else
- X toread[ng] = TR_NONE;
- X#endif
- X }
- X else {
- X#ifdef VERBOSE
- X IF(verbose)
- X printf("\nNewsgroup %s is not currently accessible.\n",
- X ngname) FLUSH;
- X ELSE
- X#endif
- X#ifdef TERSE
- X printf("\n%s not readable.\n",ngname) FLUSH;
- X#endif
- X toread[ng] = TR_NONE; /* make this newsgroup invisible */
- X /* (temporarily) */
- X }
- X mode = oldmode;
- X return -1;
- X }
- X
- X /* chdir to newsgroup subdirectory */
- X
- X if (chdir(ngdir)) {
- X printf(nocd,ngdir) FLUSH;
- X mode = oldmode;
- X return -1;
- X }
- X#endif /* SERVER */
- X
- X#ifdef CACHESUBJ
- X subj_list = Null(char **); /* no subject list till needed */
- X#endif
- X
- X /* initialize control bitmap */
- X
- X if (initctl()) {
- X mode = oldmode;
- X return -1;
- X }
- X
- X /* FROM HERE ON, RETURN THRU CLEANUP OR WE ARE SCREWED */
- X
- X in_ng = TRUE; /* tell the world we are here */
- X forcelast = TRUE; /* if 0 unread, do not bomb out */
- X
- X#ifdef USETHREADS
- X if (use_threads) /* grab thread data */
- X ThreadedGroup = use_data(ThreadedGroup);
- X#endif
- X
- X /* remember what newsgroup we were in for sake of posterity */
- X
- X writelast();
- X
- X /* see if there are any special searches to do */
- X
- X#ifdef KILLFILES
- X open_kfile(KF_LOCAL);
- X#ifdef VERBOSE
- X IF(verbose)
- X kill_unwanted(firstart,"Looking for articles to kill...\n\n",TRUE);
- X ELSE
- X#endif
- X#ifdef TERSE
- X kill_unwanted(firstart,"Killing...\n\n",TRUE);
- X#endif
- X#endif
- X#ifdef USETHREADS
- X first_art();
- X#else
- X art=firstart;
- X#endif
- X
- X /* do they want a special top line? */
- X
- X firstline = getval("FIRSTLINE",Nullch);
- X
- X /* custom line suppression, custom page ending */
- X
- X#ifdef CUSTOMLINES
- X if (hideline = getval("HIDELINE",Nullch))
- X compile(&hide_compex,hideline,TRUE,TRUE);
- X if (pagestop = getval("PAGESTOP",Nullch))
- X compile(&page_compex,pagestop,TRUE,TRUE);
- X#endif
- X
- X /* now read each unread article */
- X
- X rc_changed = doing_ng = TRUE; /* enter the twilight zone */
- X skipstate = 0; /* we have not skipped anything (yet) */
- X checkcount = 0; /* do not checkpoint for a while */
- X do_fseek = FALSE; /* start 1st article at top */
- X if (art > lastart)
- X#ifdef USETHREADS
- X first_art();
- X#else
- X art=firstart; /* init the for loop below */
- X#endif
- X for (; art<=lastart+1; ) { /* for each article */
- X
- X /* do we need to "grow" the newsgroup? */
- X
- X#ifdef USETHREADS
- X if (ThreadedGroup) {
- X if ((art > lastart || forcegrow) && getngsize(ng) > total.last) {
- X if (art > lastart)
- X art = 0;
- X unuse_data(1); /* free data with selections saved */
- X ThreadedGroup = use_data(TRUE); /* grows ctl & sets lastart */
- X if (!art)
- X if (!forcelast)
- X first_art();
- X else
- X art = lastart+1;
- X find_article(art);
- X curr_p_art = p_art;
- X forcegrow = FALSE;
- X }
- X }
- X else
- X#endif
- X if (art > lastart || forcegrow)
- X grow_ctl(getngsize(ng));
- X check_first(art); /* make sure firstart is still 1st */
- X if (start_command) { /* fake up an initial command? */
- X prompt = whatnext;
- X strcpy(buf,start_command);
- X free(start_command);
- X start_command = Nullch;
- X#ifdef USETHREADS
- X p_art = Nullart;
- X#endif
- X art = lastart+1;
- X goto article_level;
- X }
- X if (art>lastart) { /* are we off the end still? */
- X ART_NUM ucount = 0; /* count of unread articles left */
- X
- X for (i=firstart; i<=lastart; i++)
- X if (!(ctl_read(i)))
- X ucount++; /* count the unread articles */
- X#ifdef DEBUGGING
- X /*NOSTRICT*/
- X if (debug && ((ART_NUM)toread[ng]) != ucount)
- X printf("(toread=%ld sb %ld)",(long)toread[ng],(long)ucount)
- X FLUSH;
- X#endif
- X /*NOSTRICT*/
- X toread[ng] = (ART_UNREAD)ucount; /* this is perhaps pointless */
- X art = lastart + 1; /* keep bitmap references sane */
- X if (art != curr_art) {
- X#ifdef USETHREADS
- X recent_p_art = curr_p_art;
- X find_article(art);
- X curr_p_art = p_art;
- X#endif
- X recent_art = curr_art;
- X /* remember last article # (for '-') */
- X curr_art = art; /* remember this article # */
- X }
- X#ifdef USETHREADS
- X if (ThreadedGroup)
- X ucount -= unthreaded;
- X if (!forcelast && selected_root_cnt && !selected_count && ucount) {
- X strcpy(buf, "+");
- X prompt = whatnext;
- X goto article_level;
- X }
- X#endif
- X if (erase_screen)
- X clear(); /* clear the screen */
- X else
- X fputs("\n\n",stdout) FLUSH;
- X#ifdef VERBOSE
- X IF(verbose)
- X printf("End of newsgroup %s.",ngname);
- X /* print pseudo-article */
- X ELSE
- X#endif
- X#ifdef TERSE
- X printf("End of %s",ngname);
- X#endif
- X if (ucount) {
- X#ifdef USETHREADS
- X if (selected_root_cnt)
- X printf(" (%ld + %ld articles still unread)",
- X (long)selected_count,(long)ucount-selected_count);
- X else
- X#endif
- X printf(" (%ld article%s still unread)",
- X (long)ucount,ucount==1?nullstr:"s");
- X }
- X else {
- X#if defined(USETHREADS) && !defined(USETMPTHREAD)
- X if (tobethreaded)
- X printf(" (%d article%s not yet threaded)",
- X tobethreaded, tobethreaded == 1 ? nullstr : "s") FLUSH;
- X#endif
- X if (!forcelast)
- X goto cleanup; /* actually exit newsgroup */
- X }
- X prompt = whatnext;
- X#ifdef ARTSEARCH
- X srchahead = 0; /* no more subject search mode */
- X#endif
- X fputs("\n\n",stdout) FLUSH;
- X skipstate = 0; /* back to none skipped */
- X }
- X else if (!reread && was_read(art)) {
- X /* has this article been read? */
- X#ifdef USETHREADS
- X follow_thread('n');
- X#else
- X art++; /* then skip it */
- X#endif
- X continue;
- X }
- X else if
- X (!reread && !was_read(art)
- X#ifdef SERVER
- X && nntpopen(art,GET_HEADER) == Nullfp) {
- X#else
- X && artopen(art) == Nullfp) { /* never read it, & cannot find it? */
- X if (errno != ENOENT) { /* has it not been deleted? */
- X#ifdef VERBOSE
- X IF(verbose)
- X printf("\n(Article %ld exists but is unreadable.)\n",
- X (long)art) FLUSH;
- X ELSE
- X#endif /* VERBOSE */
- X#ifdef TERSE
- X printf("\n(%ld unreadable.)\n",(long)art) FLUSH;
- X#endif /* TERSE */
- X skipstate = 0;
- X sleep(2);
- X }
- X#endif /* SERVER */
- X switch(skipstate++) {
- X case 0:
- X clear();
- X#ifdef VERBOSE
- X IF(verbose)
- X fputs("Skipping unavailable article",stdout);
- X ELSE
- X#endif /* VERBOSE */
- X#ifdef TERSE
- X fputs("Skipping",stdout);
- X#endif /* TERSE */
- X pad(just_a_sec/3);
- X sleep(1);
- X break;
- X case 1:
- X fputs("..",stdout);
- X fflush(stdout);
- X break;
- X default:
- X putchar('.');
- X fflush(stdout);
- X#ifndef SERVER
- X#define READDIR
- X#ifdef READDIR
- X { /* fast skip patch */
- X ART_NUM newart;
- X
- X if (! (newart=getngmin(".",art)))
- X newart = lastart+1;
- X for (i=art; i<newart; i++)
- X oneless(i);
- X#ifndef USETHREADS
- X art = newart - 1;
- X#endif
- X }
- X#endif /* READDIR */
- X#else
- X {
- X char ser_line[NNTP_STRLEN];
- X ART_NUM newart;
- X
- X if (isfirstart) {
- X sprintf(ser_line, "STAT %d",absfirst);
- X put_server(ser_line);
- X if (nntp_get(ser_line, sizeof(ser_line)) < 0) {
- X fprintf(stderr,
- X "\nrrn: Unexpected close of server socket.\n");
- X finalize(1);
- X }
- X newart=absfirst;
- X isfirstart=FALSE;
- X }
- X else {
- X put_server("NEXT");
- X if (nntp_get(ser_line, sizeof(ser_line)) < 0) {
- X fprintf(stderr,
- X "\nrrn: unexpected close of server socket.\n");
- X finalize(1);
- X }
- X if (ser_line[0] != CHAR_OK) {
- X newart = lastart + 1;
- X }
- X else
- X newart = atoi(ser_line+4);
- X }
- X for (i=art; i<newart; i++)
- X oneless(i);
- X#ifndef USETHREADS
- X art = newart - 1;
- X#endif
- X }
- X#endif /* SERVER */
- X break;
- X }
- X oneless(art); /* mark deleted as read */
- X#ifdef USETHREADS
- X count_roots(FALSE); /* Keep selected_count accurate */
- X find_article(art);
- X follow_thread('n');
- X#else
- X art++; /* try next article */
- X#endif
- X continue;
- X }
- X else { /* we have a real live article */
- X skipstate = 0; /* back to none skipped */
- X if (art != curr_art) {
- X#ifdef USETHREADS
- X recent_p_art = curr_p_art;
- X find_article(art);
- X curr_p_art = p_art;
- X#endif
- X recent_art = curr_art;
- X /* remember last article # (for '-') */
- X curr_art = art; /* remember this article # */
- X }
- X if (!do_fseek) { /* starting at top of article? */
- X artline = 0; /* start at the beginning */
- X topline = -1; /* and remember top line of screen */
- X /* (line # within article file) */
- X }
- X clear(); /* clear screen */
- X if (!artopen(art)) { /* make sure article is found & open */
- X#ifdef USETHREADS
- X char tmpbuf[256];
- X /* see if we have tree data for this article anyway */
- X init_tree();
- X sprintf(tmpbuf,"%s #%ld is not available.",ngname,(long)art);
- X tree_puts(tmpbuf,0,0);
- X vwtary((ART_LINE)0,(ART_POS)0);
- X finish_tree(1);
- X prompt = whatnext;
- X#else
- X printf("Article %ld of %s is not available.\n\n",
- X (long)art,ngname) FLUSH;
- X prompt = whatnext;
- X#endif
- X#ifdef ARTSEARCH
- X srchahead = 0;
- X#endif
- X }
- X else { /* found it, so print it */
- X switch (do_article()) {
- X case DA_CLEAN: /* quit newsgroup */
- X goto cleanup;
- X case DA_TOEND: /* do not mark as read */
- X goto reask_article;
- X case DA_RAISE: /* reparse command at end of art */
- X goto article_level;
- X case DA_NORM: /* normal end of article */
- X break;
- X }
- X }
- X if (art >= absfirst) /* don't mark non-existant articles */
- X mark_as_read(); /* mark current article as read */
- X do_hiding = TRUE;
- X#ifdef ROTATION
- X rotate = FALSE;
- X#endif
- X }
- X
- X/* if these gotos bother you, think of this as a little state machine */
- X
- reask_article:
- X#ifdef MAILCALL
- X setmail();
- X#endif
- X setdfltcmd();
- X#ifdef CLEAREOL
- X if (erase_screen && can_home_clear)
- X clear_rest();
- X#endif /* CLEAREOL */
- X unflush_output(); /* disable any ^O in effect */
- X standout(); /* enter standout mode */
- X printf(prompt,mailcall,dfltcmd);/* print prompt, whatever it is */
- X un_standout(); /* leave standout mode */
- X putchar(' ');
- X fflush(stdout);
- reinp_article:
- X reread = FALSE;
- X forcelast = FALSE;
- X eat_typeahead();
- X#ifdef PENDING
- X look_ahead(); /* see what we can do in advance */
- X if (!input_pending())
- X collect_subjects(); /* loads subject cache until */
- X /* input is pending */
- X#endif
- X getcmd(buf);
- X if (errno || *buf == '\f') {
- X if (LINES < 100 && !int_count)
- X *buf = '\f'; /* on CONT fake up refresh */
- X else {
- X putchar('\n') FLUSH; /* but only on a crt */
- X goto reask_article;
- X }
- X }
- article_level:
- X#ifdef USETHREADS
- X output_chase_phrase = TRUE;
- X#endif
- X
- X /* parse and process article level command */
- X
- X switch (art_switch()) {
- X case AS_INP: /* multichar command rubbed out */
- X goto reinp_article;
- X case AS_ASK: /* reprompt "End of article..." */
- X goto reask_article;
- X case AS_CLEAN: /* exit newsgroup */
- X goto cleanup;
- X case AS_NORM: /* display article art */
- X break;
- X }
- X } /* end of article selection loop */
- X
- X/* shut down newsgroup */
- X
- cleanup:
- X decode_end();
- X#ifdef KILLFILES
- X kill_unwanted(firstart,"\nCleaning up...\n\n",FALSE);
- X /* do cleanup from KILL file, if any */
- X#endif
- X#ifdef USETHREADS
- X if (ThreadedGroup)
- X unuse_data(0); /* free article thread data */
- X#endif
- X in_ng = FALSE; /* leave newsgroup state */
- X if (artfp != Nullfp) { /* article still open? */
- X fclose(artfp); /* close it */
- X artfp = Nullfp; /* and tell the world */
- X#ifdef SERVER
- X interp(intrpwork,MAXFILENAME-1, "%P");
- X sprintf(artname, "%s/rrn%ld.%ld", intrpwork,(long) openart, our_pid);
- X UNLINK(artname);
- X#endif /* SERVER */
- X openart = 0;
- X }
- X putchar('\n') FLUSH;
- X#ifdef DELAYMARK
- X yankback(); /* do a Y command */
- X#endif
- X restore_ng(); /* reconstitute .newsrc line */
- X doing_ng = FALSE; /* tell sig_catcher to cool it */
- X free(ctlarea); /* return the control area */
- X#ifdef CACHESUBJ
- X if (subj_list) {
- X for (i=OFFSET(lastart); i>=0; --i)
- X if (subj_list[i])
- X free(subj_list[i]);
- X#ifndef lint
- X free((char*)subj_list);
- X#endif /* lint */
- X }
- X#endif
- X write_rc(); /* and update .newsrc */
- X rc_changed = FALSE; /* tell sig_catcher it is ok */
- X if (chdir(spool)) {
- X printf(nocd,spool) FLUSH;
- X sig_catcher(0);
- X }
- X#ifdef KILLFILES
- X if (localkfp) {
- X fclose(localkfp);
- X localkfp = Nullfp;
- X }
- X#endif
- X mode = oldmode;
- X return exit_code;
- X} /* Whew! */
- X
- X/* decide what to do at the end of an article */
- X
- int
- art_switch()
- X{
- X register ART_NUM i;
- X
- X setdef(buf,dfltcmd);
- X#ifdef VERIFY
- X printcmd();
- X#endif
- X
- X switch (*buf) {
- X#ifdef USETHREADS
- X case '<': /* goto previous thread */
- X if (!ThreadedGroup) {
- X goto group_unthreaded;
- X }
- X prev_root();
- X return AS_NORM;
- X case '>': /* goto next thread */
- X if (!ThreadedGroup) {
- X goto group_unthreaded;
- X }
- X next_root();
- X return AS_NORM;
- X case 'U': { /* unread some articles */
- X char *u_prompt, *u_help_thread;
- X
- X if (!ThreadedGroup) {
- X dfltcmd = "a";
- X u_help_thread = nullstr;
- X#ifdef VERBOSE
- X IF(verbose)
- X u_prompt = "\nSet unread: all articles? [an] ";
- X ELSE
- X#endif
- X#ifdef TERSE
- X u_prompt = "\nUnread? [an] ";
- X#endif
- X }
- X else if (!p_art || art > lastart) {
- X dfltcmd = "+";
- X u_help_thread = nullstr;
- X#ifdef VERBOSE
- X IF(verbose)
- X u_prompt = "\nSet unread: +select or all? [+an] ";
- X ELSE
- X#endif
- X#ifdef TERSE
- X u_prompt = "\nUnread? [+an] ";
- X#endif
- X }
- X else {
- X dfltcmd = "+";
- X#ifdef VERBOSE
- X IF(verbose) {
- X u_prompt = "\n\
- Set unread: +select, thread, subthread, or all? [+tsan] ";
- X u_help_thread = "\
- Type t or SP to mark this thread's articles as unread.\n\
- Type s to mark the current article and its descendants as unread.\n";
- X }
- X ELSE
- X#endif
- X#ifdef TERSE
- X {
- X u_prompt = "\nUnread? [ts+an] ";
- X u_help_thread = "\
- t or SP to mark thread unread.\n\
- s to mark subthread unread.\n";
- X }
- X#endif
- X }
- X reask_unread:
- X in_char(u_prompt,'u');
- X setdef(buf,dfltcmd);
- X#ifdef VERIFY
- X printcmd();
- X#endif
- X putchar('\n') FLUSH;
- X if (*buf == 'h') {
- X#ifdef VERBOSE
- X IF(verbose)
- X {
- X if (ThreadedGroup)
- X fputs("\
- Type + to enter select thread mode using all the unread articles.\n\
- X(The selected threads will be marked as unread and displayed as usual.)\n\
- X",stdout) FLUSH;
- X fputs(u_help_thread,stdout);
- X fputs("\
- Type a to mark all articles in this group as unread.\n\
- Type n to change nothing.\n\
- X",stdout) FLUSH;
- X }
- X ELSE
- X#endif
- X#ifdef TERSE
- X {
- X if (ThreadedGroup)
- X fputs("\
- X+ to select threads from the unread.\n\
- X",stdout) FLUSH;
- X fputs(u_help_thread,stdout);
- X fputs("\
- a to mark all articles unread.\n\
- n to change nothing.\n\
- X",stdout) FLUSH;
- X }
- X#endif
- X goto reask_unread;
- X }
- X else if (*buf == 'n' || *buf == 'q') {
- X return AS_ASK;
- X }
- X else if (*buf == 't' && u_help_thread != nullstr)
- X follow_thread('u');
- X else if (*buf == 's' && u_help_thread != nullstr)
- X follow_thread('U');
- X else if (*buf == 'a') {
- X check_first(absfirst);
- X for (i = absfirst; i <= lastart; i++) {
- X onemore(i); /* mark as unread */
- X }
- X scan_all_roots = FALSE;
- X count_roots(FALSE);
- X if (art > lastart) {
- X first_art();
- X }
- X }
- X else if (ThreadedGroup && *buf == '+') {
- X *buf = 'U';
- X goto select_threads;
- X }
- X else {
- X fputs(hforhelp,stdout) FLUSH;
- X settle_down();
- X goto reask_unread;
- X }
- X return AS_NORM;
- X }
- X case '[': /* goto parent article */
- X case '{': /* goto thread's root article */
- X if (p_art) {
- X if (!p_art->parent) {
- X if (p_art == p_articles + p_roots[p_art->root].articles) {
- X register char *cp = (*buf=='['?"parent":"root");
- X#ifdef VERBOSE
- X IF(verbose)
- X fprintf(stdout,"\n\
- There is no %s article prior to this one.\n",cp) FLUSH;
- X ELSE
- X#endif
- X#ifdef TERSE
- X fprintf(stdout,"\nNo prior %s.\n",cp) FLUSH;
- X#endif
- X return AS_ASK;
- X }
- X *buf = '{';
- X p_art--;
- X }
- X else
- X p_art += p_art->parent;
- X
- X if (*buf == '{')
- X while (p_art->parent)
- X p_art += p_art->parent;
- X
- X art = p_art->num;
- X reread = TRUE;
- X return AS_NORM;
- X }
- not_threaded:
- X if (ThreadedGroup) {
- X#ifdef VERBOSE
- X IF(verbose)
- X fputs("\nThis article is not threaded.\n",stdout) FLUSH;
- X ELSE
- X#endif
- X#ifdef TERSE
- X fputs("\nUnthreaded article.\n",stdout) FLUSH;
- X#endif
- X return AS_ASK;
- X }
- group_unthreaded:
- X#ifdef VERBOSE
- X IF(verbose)
- X fputs("\nThis group is not threaded.\n",stdout) FLUSH;
- X ELSE
- X#endif
- X#ifdef TERSE
- X fputs("\nUnthreaded group.\n",stdout) FLUSH;
- X#endif
- X return AS_ASK;
- X case ']': /* goto child article */
- X case '}': /* goto thread's leaf article */
- X if (p_art) {
- X if (!(p_art++)->child_cnt) {
- X PACKED_ARTICLE *root_limit = upper_limit(p_art-1,FALSE);
- X
- X if (p_art == root_limit) {
- X#ifdef VERBOSE
- X IF(verbose)
- X fputs("\n\
- This is the last leaf in this tree.\n",stdout) FLUSH;
- X ELSE
- X#endif
- X#ifdef TERSE
- X fputs("\nLast leaf.\n",stdout) FLUSH;
- X#endif
- X p_art--;
- X return AS_ASK;
- X }
- X if (*buf == ']')
- X *buf = '}';
- X else {
- X while (++p_art != root_limit && p_art->parent)
- X ;
- X p_art--;
- X *buf = ' ';
- X }
- X }
- X if (*buf == '}')
- X while (p_art->child_cnt)
- X p_art++;
- X
- X art = p_art->num;
- X reread = TRUE;
- X return AS_NORM;
- X }
- X goto not_threaded;
- X case 'T':
- X if (p_art) {
- X sprintf(buf,"T%ld\t# %s",(long)p_roots[p_art->root].root_num,
- X subject_ptrs[p_art->subject]);
- X fputs(buf,stdout);
- X kf_append(buf);
- X follow_thread('J');
- X return AS_NORM;
- X }
- X goto not_threaded;
- X case 'K':
- X if (p_art) {
- X /* first, write kill-subject command */
- X (void)art_search(buf, (sizeof buf), TRUE);
- X art = curr_art;
- X p_art = curr_p_art;
- X follow_thread('k'); /* then take care of any prior subjs */
- X return AS_NORM;
- X }
- X goto normal_search;
- X case ',': /* kill this node and all descendants */
- X mark_as_read();
- X *buf = 'K';
- X case 'k': /* kill current subject # (e.g. [1]) */
- X case 'J': /* Junk all nodes in this thread */
- X if (ThreadedGroup) {
- X follow_thread(*buf);
- X return AS_NORM;
- X }
- X *buf = 'k';
- X goto normal_search;
- X case 't':
- X carriage_return();
- X#ifndef CLEAREOL
- X erase_eol(); /* erase the prompt */
- X#else
- X if (erase_screen && can_home_clear)
- X clear_rest();
- X else
- X erase_eol(); /* erase the prompt */
- X#endif /* CLEAREOL */
- X fflush(stdout);
- X page_line = 1;
- X p_art = curr_p_art;
- X entire_tree();
- X return AS_ASK;
- X case ':': /* execute command on selected articles */
- X if (!ThreadedGroup) {
- X goto group_unthreaded;
- X }
- X page_line = 1;
- X if (!use_selected())
- X return AS_INP;
- X putchar('\n');
- X art = curr_art;
- X p_art = curr_p_art;
- X return AS_ASK;
- X#endif /* USETHREADS */
- X case 'p': /* find previous unread article */
- X#ifdef USETHREADS
- X if (ThreadedGroup) {
- X goto backtrack_threads;
- X }
- X#endif
- X do {
- X if (art <= firstart)
- X break;
- X art--;
- X#ifdef SERVER
- X } while (was_read(art) || nntpopen(art,GET_HEADER) == Nullfp);
- X#else
- X } while (was_read(art) || artopen(art) == Nullfp);
- X#endif
- X#ifdef ARTSEARCH
- X srchahead = 0;
- X#endif
- X return AS_NORM;
- X case 'P': /* goto previous article */
- X#ifdef USETHREADS
- X if (ThreadedGroup) {
- backtrack_threads:
- X backtrack_thread(*buf);
- X art++; /* prepare for art-- below */
- X }
- X#endif
- X if (art > absfirst)
- X art--;
- X else {
- X#ifdef VERBOSE
- X IF(verbose)
- X fprintf(stdout,"\n\
- There are no%s articles prior to this one.\n\
- X",*buf=='P'?nullstr:" unread") FLUSH;
- X ELSE
- X#endif
- X#ifdef TERSE
- X fprintf(stdout,"\n\
- No previous%s articles\n\
- X",*buf=='P'?nullstr:" unread") FLUSH;
- X#endif
- X art = curr_art;
- X#ifdef USETHREADS
- X p_art = curr_p_art;
- X#endif
- X return AS_ASK;
- X }
- X reread = TRUE;
- X#ifdef ARTSEARCH
- X srchahead = 0;
- X#endif
- X return AS_NORM;
- X case '-':
- X if (recent_art >= 0) {
- X#ifdef USETHREADS
- X p_art = recent_p_art;
- X#endif
- X art = recent_art;
- X reread = TRUE;
- X#ifdef ARTSEARCH
- X srchahead = -(srchahead != 0);
- X#endif
- X return AS_NORM;
- X }
- X else {
- X exit_code = NG_MINUS;
- X return AS_CLEAN;
- X }
- X case 'n': /* find next unread article? */
- X#ifdef USETHREADS
- X if (ThreadedGroup) {
- X follow_thread(*buf);
- X return AS_NORM;
- X }
- X#endif
- X if (art > lastart) {
- X if (!toread[ng])
- X return AS_CLEAN;
- X art = firstart;
- X }
- X#ifdef ARTSEARCH
- X else if (scanon && srchahead) {
- X *buf = Ctl('n');
- X goto normal_search;
- X }
- X#endif
- X else
- X art++;
- X
- X#ifdef ARTSEARCH
- X srchahead = 0;
- X#endif
- X return AS_NORM;
- X case 'N': /* goto next article */
- X#ifdef USETHREADS
- X if (ThreadedGroup) {
- X follow_thread(*buf);
- X return AS_NORM;
- X }
- X#endif
- X if (art > lastart)
- X art = absfirst;
- X else
- X art++;
- X if (art <= lastart)
- X reread = TRUE;
- X#ifdef ARTSEARCH
- X srchahead = 0;
- X#endif
- X return AS_NORM;
- X case '$':
- X art = lastart+1;
- X forcelast = TRUE;
- X#ifdef USETHREADS
- X p_art = Nullart;
- X#endif
- X#ifdef ARTSEARCH
- X srchahead = 0;
- X#endif
- X return AS_NORM;
- X case '1': case '2': case '3': /* goto specified article */
- X case '4': case '5': case '6': /* or do something with a range */
- X case '7': case '8': case '9': case '.':
- X forcelast = TRUE;
- X switch (numnum()) {
- X case NN_INP:
- X return AS_INP;
- X case NN_ASK:
- X return AS_ASK;
- X case NN_REREAD:
- X reread = TRUE;
- X#ifdef ARTSEARCH
- X if (srchahead)
- X srchahead = -1;
- X#endif
- X break;
- X case NN_NORM:
- X if (was_read(art)) {
- X#ifdef USETHREADS
- X first_art();
- X#else
- X art = firstart;
- X#endif
- X pad(just_a_sec/3);
- X }
- X else {
- X putchar('\n');
- X return AS_ASK;
- X }
- X break;
- X }
- X return AS_NORM;
- X case Ctl('k'):
- X edit_kfile();
- X return AS_ASK;
- X#ifndef USETHREADS
- X case 'K':
- X case 'k':
- X#endif
- X case Ctl('n'): /* search for next article with same subject */
- X#ifdef USETHREADS
- X if (ThreadedGroup) {
- X follow_thread(*buf);
- X return AS_NORM;
- X }
- X#endif
- X case Ctl('p'): /* search for previous article with same subject */
- X#ifdef USETHREADS
- X if (ThreadedGroup) {
- X goto backtrack_threads;
- X }
- X#endif
- X case '/': case '?':
- normal_search:
- X#ifdef ARTSEARCH
- X { /* search for article by pattern */
- X char cmd = *buf;
- X
- X reread = TRUE; /* assume this */
- X page_line = 1;
- X switch (art_search(buf, (sizeof buf), TRUE)) {
- X case SRCH_ERROR:
- X art = curr_art;
- X return AS_ASK;
- X case SRCH_ABORT:
- X art = curr_art;
- X return AS_INP;
- X case SRCH_INTR:
- X#ifdef VERBOSE
- X IF(verbose)
- X printf("\n(Interrupted at article %ld)\n",(long)art) FLUSH;
- X ELSE
- X#endif
- X#ifdef TERSE
- X printf("\n(Intr at %ld)\n",(long)art) FLUSH;
- X#endif
- X art = curr_art;
- X /* restore to current article */
- X return AS_ASK;
- X case SRCH_DONE:
- X fputs("done\n",stdout) FLUSH;
- X pad(just_a_sec/3); /* 1/3 second */
- X if (!srchahead) {
- X art = curr_art;
- X return AS_ASK;
- X }
- X#ifdef USETHREADS
- X first_art();
- X#else
- X art = firstart;
- X#endif
- X reread = FALSE;
- X return AS_NORM;
- X case SRCH_SUBJDONE:
- X#ifdef UNDEF
- X fputs("\n\n\n\nSubject not found.\n",stdout) FLUSH;
- X pad(just_a_sec/3); /* 1/3 second */
- X#endif
- X#ifdef USETHREADS
- X first_art();
- X#else
- X art = firstart;
- X#endif
- X reread = FALSE;
- X return AS_NORM;
- X case SRCH_NOTFOUND:
- X fputs("\n\n\n\nNot found.\n",stdout) FLUSH;
- X art = curr_art; /* restore to current article */
- X return AS_ASK;
- X case SRCH_FOUND:
- X if (cmd == Ctl('n') || cmd == Ctl('p'))
- X oldsubject = TRUE;
- X break;
- X }
- X return AS_NORM;
- X }
- X#else
- X buf[1] = '\0';
- X notincl(buf);
- X return AS_ASK;
- X#endif
- X case 'u': /* unsubscribe from this newsgroup? */
- X rcchar[ng] = NEGCHAR;
- X return AS_CLEAN;
- X case 'M':
- X#ifdef DELAYMARK
- X if (art <= lastart) {
- X delay_unmark(art);
- X printf("\nArticle %ld will return.\n",(long)art) FLUSH;
- X }
- X#else
- X notincl("M");
- X#endif
- X return AS_ASK;
- X case 'm':
- X if (art <= lastart) {
- X unmark_as_read();
- X printf("\nArticle %ld marked as still unread.\n",(long)art) FLUSH;
- X }
- X return AS_ASK;
- X case 'c': /* catch up */
- X reask_catchup:
- X#ifdef VERBOSE
- X IF(verbose)
- X in_char("\nDo you really want to mark everything as read? [yn] ",
- X 'C');
- X ELSE
- X#endif
- X#ifdef TERSE
- X in_char("\nReally? [ynh] ", 'C');
- X#endif
- X setdef(buf,"y");
- X#ifdef VERIFY
- X printcmd();
- X#endif
- X putchar('\n') FLUSH;
- X if (*buf == 'h') {
- X#ifdef VERBOSE
- X IF(verbose)
- X fputs("\
- Type y or SP to mark all articles as read.\n\
- Type n to leave articles marked as they are.\n\
- Type u to mark everything read and unsubscribe.\n\
- X",stdout) FLUSH;
- X ELSE
- X#endif
- X#ifdef TERSE
- X fputs("\
- y or SP to mark all read.\n\
- n to forget it.\n\
- u to mark all and unsubscribe.\n\
- X",stdout) FLUSH;
- X#endif
- X goto reask_catchup;
- X }
- X else if (*buf == 'n' || *buf == 'q') {
- X return AS_ASK;
- X }
- X else if (*buf != 'y' && *buf != 'u') {
- X fputs(hforhelp,stdout) FLUSH;
- X settle_down();
- X goto reask_catchup;
- X }
- X for (i = firstart; i <= lastart; i++) {
- X oneless(i); /* mark as read */
- X }
- X#ifdef USETHREADS
- X selected_count = selected_root_cnt = unthreaded = 0;
- X#endif
- X#ifdef DELAYMARK
- X if (dmfp)
- X yankback();
- X#endif
- X if (*buf == 'u') {
- X rcchar[ng] = NEGCHAR;
- X return AS_CLEAN;
- X }
- X#ifdef USETHREADS
- X p_art = Nullart;
- X#endif
- X art = lastart+1;
- X forcelast = FALSE;
- X return AS_NORM;
- X case 'Q':
- X exit_code = NG_ASK;
- X /* FALL THROUGH */
- X case 'q': /* go back up to newsgroup level? */
- X return AS_CLEAN;
- X case 'j':
- X putchar('\n') FLUSH;
- X if (art <= lastart)
- X mark_as_read();
- X return AS_ASK;
- X case 'h': { /* help? */
- X int cmd;
- X
- X if ((cmd = help_art()) > 0)
- X pushchar(cmd);
- X return AS_ASK;
- X }
- X case '&':
- X if (switcheroo()) /* get rest of command */
- X return AS_INP; /* if rubbed out, try something else */
- X return AS_ASK;
- X case '#':
- X#ifdef VERBOSE
- X IF(verbose)
- X printf("\nThe last article is %ld.\n",(long)lastart) FLUSH;
- X ELSE
- X#endif
- X#ifdef TERSE
- X printf("\n%ld\n",(long)lastart) FLUSH;
- X#endif
- X return AS_ASK;
- X#ifdef USETHREADS
- X case '+': /* enter thread selection mode */
- X if (ThreadedGroup) {
- select_threads:
- X *buf = select_thread(*buf);
- X switch (*buf) {
- X case '+':
- X case '\033':
- X putchar('\n') FLUSH;
- X return AS_ASK;
- X case 'Q':
- X exit_code = NG_ASK;
- X /* FALL THROUGH */
- X case 'q':
- X break;
- X case 'N':
- X exit_code = NG_SELNEXT;
- X break;
- X case 'P':
- X exit_code = NG_SELPRIOR;
- X break;
- X default:
- X if (toread[ng])
- X return AS_NORM;
- X break;
- X }
- X return AS_CLEAN;
- X }
- X /* FALL THROUGH */
- X#endif
- X case '=': { /* list subjects */
- X char tmpbuf[256];
- X ART_NUM oldart = art;
- X int cmd;
- X char *subjline = getval("SUBJLINE",Nullch);
- X#ifndef CACHESUBJ
- X char *s;
- X#endif
- X
- X page_init();
- X#ifdef CACHESUBJ
- X if (!subj_list)
- X fetchsubj(art,TRUE,FALSE);
- X#endif
- X for (i=firstart; i<=lastart && !int_count; i++) {
- X#ifdef CACHESUBJ
- X if (!was_read(i) &&
- X (subj_list[OFFSET(i)] != Nullch || fetchsubj(i,FALSE,FALSE)) &&
- X *subj_list[OFFSET(i)] ) {
- X sprintf(tmpbuf,"%5ld ", i);
- X if (subjline) {
- X art = i;
- X interp(tmpbuf + 6, (sizeof tmpbuf) - 6, subjline);
- X }
- X else
- X safecpy(tmpbuf + 6, subj_list[OFFSET(i)],
- X (sizeof tmpbuf) - 6);
- X if (cmd = print_lines(tmpbuf,NOMARKING)) {
- X if (cmd > 0)
- X pushchar(cmd);
- X break;
- X }
- X }
- X#else
- X if (!was_read(i) && (s = fetchsubj(i,FALSE,FALSE)) && *s) {
- X sprintf(tmpbuf,"%5ld ", i);
- X if (subjline) { /* probably fetches it again! */
- X art = i;
- X interp(tmpbuf + 6, (sizeof tmpbuf) - 6, subjline);
- X }
- X else
- X safecpy(tmpbuf + 6, s, (sizeof tmpbuf) - 6);
- X if (cmd = print_lines(tmpbuf,NOMARKING)) {
- X if (cmd > 0)
- X pushchar(cmd);
- X break;
- X }
- X }
- X#endif
- X }
- X int_count = 0;
- X art = oldart;
- X return AS_ASK;
- X }
- X case '^':
- X#ifdef USETHREADS
- X first_art();
- X#else
- X art = firstart;
- X#endif
- X#ifdef ARTSEARCH
- X srchahead = 0;
- X#endif
- X return AS_NORM;
- X#if defined(CACHESUBJ) && defined(DEBUGGING)
- X case 'D':
- X printf("\nFirst article: %ld\n",(long)firstart) FLUSH;
- X if (!subj_list)
- X fetchsubj(art,TRUE,FALSE);
- X if (subj_list != Null(char **)) {
- X for (i=1; i<=lastart && !int_count; i++) {
- X if (subj_list[OFFSET(i)])
- X printf("%5ld %c %s\n",
- X i, (was_read(i)?'y':'n'), subj_list[OFFSET(i)]) FLUSH;
- X }
- X }
- X int_count = 0;
- X return AS_ASK;
- X#endif
- X case 'v':
- X if (art <= lastart) {
- X reread = TRUE;
- X do_hiding = FALSE;
- X }
- X return AS_NORM;
- X#ifdef ROTATION
- X case Ctl('x'):
- X#endif
- X case Ctl('r'):
- X#ifdef ROTATION
- X rotate = (*buf==Ctl('x'));
- X#endif
- X if (art <= lastart)
- X reread = TRUE;
- X else
- X forcelast = TRUE;
- X return AS_NORM;
- X#ifdef ROTATION
- X case 'X':
- X rotate = !rotate;
- X /* FALL THROUGH */
- X#else
- X case Ctl('x'):
- X case 'x':
- X case 'X':
- X notincl("x");
- X return AS_ASK;
- X#endif
- X case 'l': case Ctl('l'): /* refresh screen */
- X if (art <= lastart) {
- X reread = TRUE;
- X clear();
- X do_fseek = TRUE;
- X artline = topline;
- X if (artline < 0)
- X artline = 0;
- X }
- X return AS_NORM;
- X case 'b': case Ctl('b'): /* back up a page */
- X if (art <= lastart) {
- X ART_LINE target;
- X
- X reread = TRUE;
- X clear();
- X do_fseek = TRUE;
- X target = topline - (LINES - 2);
- X artline = topline;
- X if (artline >= 0) do {
- X artline--;
- X } while(artline >= 0 && artline > target && vrdary(artline-1) >= 0);
- X topline = artline;
- X if (artline < 0)
- X artline = 0;
- X }
- X return AS_NORM;
- X case '!': /* shell escape */
- X if (escapade())
- X return AS_INP;
- X return AS_ASK;
- X case 'C': {
- X cancel_article();
- X return AS_ASK;
- X }
- X case 'z': {
- X supersede_article(); /* supersedes */
- X return AS_ASK;
- X }
- X case 'R':
- X case 'r': { /* reply? */
- X reply();
- X return AS_ASK;
- X }
- X case 'F':
- X case 'f': { /* followup command */
- X followup();
- X forcegrow = TRUE; /* recalculate lastart */
- X return AS_ASK;
- X }
- X case '|':
- X case 'w': case 'W':
- X case 's': case 'S': /* save command */
- X case 'e': /* extract command */
- X if (save_article() == SAVE_ABORT)
- X return AS_INP;
- X int_count = 0;
- X return AS_ASK;
- X case 'E':
- X if (decode_fp)
- X decode_end();
- X else
- X putchar('\n') FLUSH;
- X return AS_ASK;
- X#ifdef DELAYMARK
- X case 'Y': /* yank back M articles */
- X yankback();
- X#ifdef USETHREADS
- X first_art();
- X#else
- X art = firstart; /* from the beginning */
- X#endif
- X return AS_NORM; /* pretend nothing happened */
- X#endif
- X#ifdef STRICTCR
- X case '\n':
- X fputs(badcr,stdout) FLUSH;
- X return AS_ASK;
- X#endif
- X default:
- X printf("\n%s",hforhelp) FLUSH;
- X settle_down();
- X return AS_ASK;
- X }
- X}
- X
- X#ifdef MAILCALL
- X/* see if there is any mail */
- X
- void
- setmail()
- X{
- X if (! (mailcount++)) {
- X char *mailfile = filexp(getval("MAILFILE",MAILFILE));
- X
- X if (stat(mailfile,&filestat) < 0 || !filestat.st_size
- X || filestat.st_atime > filestat.st_mtime)
- X mailcall = nullstr;
- X else
- X mailcall = getval("MAILCALL","(Mail) ");
- X }
- X mailcount %= 10; /* check every 10 articles */
- X}
- X#endif
- X
- void
- setdfltcmd()
- X{
- X#ifdef USETHREADS
- X if (toread[ng] == unthreaded) {
- X#else
- X if (!toread[ng]) {
- X#endif
- X if (art > lastart)
- X dfltcmd = "qnp";
- X else
- X dfltcmd = "npq";
- X }
- X else {
- X#ifdef ARTSEARCH
- X# ifdef USETHREADS
- X if (!ThreadedGroup && srchahead)
- X# else
- X if (srchahead)
- X# endif
- X dfltcmd = "^Nnpq";
- X else
- X#endif
- X dfltcmd = "npq";
- X }
- X}
- END_OF_FILE
- if test 35048 -ne `wc -c <'ng.c'`; then
- echo shar: \"'ng.c'\" unpacked with wrong size!
- fi
- # end of 'ng.c'
- fi
- echo shar: End of archive 10 \(of 13\).
- cp /dev/null ark10isdone
- MISSING=""
- for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 ; do
- if test ! -f ark${I}isdone ; then
- MISSING="${MISSING} ${I}"
- fi
- done
- if test "${MISSING}" = "" ; then
- echo You have unpacked all 13 archives.
- rm -f ark[1-9]isdone ark[1-9][0-9]isdone
- else
- echo You still need to unpack the following archives:
- echo " " ${MISSING}
- fi
- ## End of shell archive.
- exit 0
-